import base64
import pickle

from django.shortcuts import render

# Create your views here.
from django_redis import get_redis_connection
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

from carts.serializers import CartSerializer, CartSKUSerializer, CartDeleteSerializer
from goods.models import SKU
from users.models import User

'''
购物车业务需求分析
在用户登录与未登录状态下，都可以保存用户的购物车数据
用户可以对购物车数据进行增、删、改、查
用户对于购物车数据的勾选也要保存，在订单结算页面会使用勾选数据
用户登录时，合并cookie中的购物车数据到redis中

购物车技术实现
对于未登录的用户，购物车数据使用浏览器cookie保存
对于已登录的用户，购物车数据在后端使用Redis保存
'''


class CatAPIView(APIView):
    """
    POST /cart/
    cookie 的数据格式：
    {
        sku_id: {
            "count": xxx,  // 数量
            "selected": True  // 是否勾选
        },
        sku_id: {
            "count": xxx,
            "selected": False
        },
        ...
    }
    """

    # 因为访问此视图需要用户是登陆状态(token认证)，而这里允许用户在不登陆时也可以访问
    def perform_authentication(self, request):
        # 重写该方法， 确保用户不登陆访问改视图
        pass

    def post(self, request):
        """
        1. 前端 post 提交要添加的货物的商品id， 加入购物车的数量count， 是否选中（默认选中）
        2. 后端判断是否登陆， 如果登陆存储到redis， 否则存储到cookie
        """
        # 获取数据,进行校验
        serializer = CartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # 获取商品id,count和是否选中信息, 获取验证后的数据中的数据
        # request.data --> attrs --> serializer.data/validated_data
        sku_id = serializer.data.get('sku_id')
        count = serializer.data.get('count')
        selected = serializer.data.get('selected')
        try:  # 一定要 try 获取， 防止未登录用户访问时报错
            user = request.user
        except Exception:
            user = None

        # 前段传来的user数据， 并且验证权限通过才表示用户处于正常登陆状态
        if user is not None and user.is_authenticated:
            # 如果为登录用户则数据保存到redis中
            redis_conn = get_redis_connection('cart')
            pl = redis_conn.pipeline()
            # 记录购物车商品数量 hash
            pl.hincrby('cart_%s' % user.id, sku_id, count)
            # 勾选
            if selected:
                pl.sadd('cart_selected_%s' % user.id, sku_id)
            pl.execute()
            # 返回响应
            return Response(serializer.data)
        else:
        # 用户未登录， 购物车数据写入到cookie
            # 获取可能存在的cookie
            cookie_str = request.COOKIES.get('cart')
            if cookie_str is not None:
                # base64 解码
                cookie_byte = base64.b64decode(cookie_str)
                # pickle 转回字典
                cart = pickle.loads(cookie_byte)
            else:
                cart = {}  # 如果不存在就是空字典

            # 如果以前添加过商品， 再次添加就在原来基础上加累加上这次添加的数量
            if sku_id in cart:
                # 用户传来的cookie数据里的cart是已经存在的货物信息
                # request.data 是post 提交来的新家商品的数据
                original_count = cart[sku_id]['count']
                count += original_count
            # cart里存的可能不止一件商品，cart结构 cart{sku_id1:{..},sku_id2:{..},...}
            # 把新商品添加到cookie里， 如果已存的在这一步是更新数据
            cart[sku_id] = {
                'count': count,
                'selected': selected
            }

            # 对cookie加密并返回
            cart_byte = pickle.dumps(cart)  # 字典转换为二进制字符串，相当与 json.dumps
            # base64加密, 此处是二进制字节数据， 要解码
            cart = base64.b64encode(cart_byte).decode()
            response = Response(serializer.data)
            response.set_cookie('cart', cart)
            return response

    def get(self, request):
        """
        GET /cart/
        进入购物车， 获取购物车里的货物信息
        要序列化的数据： redis/cookie获取  id 	count	selected
                      数据库获取   name	default_image_url	price
        判断是否为登录用户
            登录用户,从redis中获取数据
            非登录用户,从cookie中获取数据
        获取所有商品的信息
        返回响应
        """
        try:
            user = request.user
        except Exception:
            user = None

        if user is not None and user.is_authenticated:
            # 登录用户,从redis中获取数据
            redis_conn = get_redis_connection('cart')
            redis_cart = redis_conn.hgetall('cart_%s' % user.id)
            redis_cart_select = redis_conn.smembers('cart_selected_%s' % user.id)
            cart = {}
            for sku_id, count in redis_cart.items():
                cart[int(sku_id)] = {
                    'count': int(count),
                    'selected': sku_id in redis_cart_select
                }

        else:
            # 非登录用户,从cookie中获取数据
            cart_str = request.COOKIES.get('cart')

            if cart_str is not None:
                cart = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart = {}

        # 根据商品id获取商品信息： name, default_image_url, price
        skus = SKU.objects.filter(id__in=(cart.keys()))
        # 因SKU模型类里没有count和selected字段，所以sku里也没有，序列化时就得不到这些数据，
        # 所以要添加count和selected数据到对应的查询结果中， 也就是整合cookie和数据库中的数据
        for sku in skus:
            sku.count = cart[sku.id]['count']
            sku.selected = cart[sku.id]['selected']

        serializer = CartSKUSerializer(skus,many=True)
        return Response(serializer.data)


    def put(self, request):
        """
        PUT  /cart/
        修改购物车数据
            前段传来数据： sku_id, count, selected
            的后端返回数据： sku_id, count, selected
        思路:
        # 创建序列化,校验数据
        #获取数据
        #获取用户
        #判断用户是否为登录用户
             #登录用户,从redis中获取数据
            #非登录用户,从cookie中获取数据
        """
        # 创建序列化,校验数据, 与添加是相同， 用同一个序列化器
        serializer = CartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        sku_id = serializer.data.get('sku_id')
        count = serializer.data.get('count')
        selected = serializer.data.get('selected')
        # 获取用户
        try:
            user = request.user
        except Exception:
            user = None
        # 判断用户是否为登录用户
        if user is not None and user.is_authenticated:
            # 登录用户,从redis中获取数据
            redis_conn = get_redis_connection('cart')
            pl = redis_conn.pipeline()

            # 更新数据
            pl.hset('cart_%s' % user.id, sku_id, count)
            # 更改状态
            if selected:
                pl.sadd('cart_selected_%s' % user.id, sku_id)
            else:
                pl.srem('cart_selected_%s' % user.id, sku_id)
            pl.execute()
            return Response(serializer.data)
        else:
            # 未登录用户修改数据
            cart_str = request.COOKIES.get('cart')
            if cart_str is not None:
                cart = pickle.loads(base64.b64decode(cart_str))
                # cart = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart = {}
        if sku_id in cart.keys():
            cart[sku_id] = {
                'count': count,
                'selected': selected
            }

        cookie_str = base64.b64encode(pickle.dumps(cart)).decode()
        response = Response(serializer.data)
        response.set_cookie('cart', cookie_str)
        return response

    def delete(self, request):
        """
        删除数据
        思路
        #获取提交数据,并进行校验
        #获取校验之后的数据
        #获取user信息
        #判断是否登录
            #登录用户,从redis中删除数据
            #非登录用户,从cookie中删除数据
        """
        # 获取提交数据,并进行校验
        serializer = CartDeleteSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # 获取校验之后的数据
        sku_id = serializer.data.get('sku_id')
        # 获取user信息
        try:
            user = request.user
        except Exception:
            user = None
        if user is not None and user.is_authenticated:
            # 登录用户,从redis中删除数据
            redis_conn = get_redis_connection('cart')
            pl = redis_conn.pipeline()
            pl.hdel('cart_%s' % user.id, sku_id)
            pl.srem('cart_selected_%s' % user.id, sku_id)
            pl.execute()
            return Response(status=status.HTTP_204_NO_CONTENT)
        else:
            # 非登录用户,从cookie中删除数据
            cart_str = request.COOKIES.get('cart')

            if cart_str is not None:
                cart = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                cart = {}

        response = Response(serializer.data)

        if sku_id in cart:
            del cart[sku_id]
            # 组织数据
            cookie_str = base64.b64encode(pickle.dumps(cart)).decode()
            response.set_cookie('cart', cookie_str)

        return response

